package com.zj.utils;

import com.zj.annotations.Delete;
import com.zj.annotations.Insert;
import com.zj.annotations.Select;
import com.zj.annotations.Update;
import com.zj.exception.BuilderException;
import com.zj.mapping.*;
import org.dom4j.io.SAXReader;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;

public class MapperAnnotationBuilder {

    private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>();

    private Configuration configuration;
    private Class<?> type;
    private String resource;


    public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
        this.configuration = configuration;
        this.type = type;
        this.resource=type.getName();
        sqlAnnotationTypes.add(Select.class);
        sqlAnnotationTypes.add(Insert.class);
        sqlAnnotationTypes.add(Update.class);
        sqlAnnotationTypes.add(Delete.class);
    }

    public void parse() {
        String resource = type.toString();
        //是否加载过这个类名。
        if (!configuration.isResourceLoaded(resource)) {
            //不配mapper配置的是resource还是class，都是先尝试加载resource（即xml文件）
            loadXmlResource();
            configuration.addLoadedResource(resource);
            Method[] methods = type.getMethods();
            for (Method method : methods) {
                parseStatement(method);
            }
        }
    }

    private void loadXmlResource() {
        // Spring may not know the real resource name so we check a flag
        // to prevent loading again a resource twice
        // this flag is set at XMLMapperBuilder#bindMapperForNamespace
        if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
            String xmlResource = type.getName().replace('.', '/') + ".xml";
            InputStream inputStream = null;
            try {
                inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
                if (inputStream != null) {
                    XMLMapperBuilder xmlParser = new XMLMapperBuilder(new SAXReader().read(inputStream),configuration, xmlResource);
                    xmlParser.parse();
                }
            } catch (Exception e) {
                // ignore, resource is not required
            }
        }
    }

    private void parseStatement(Method method) {
        Class<? extends Annotation> sqlAnnotationType = chooseAnnotationType(method,sqlAnnotationTypes);
        String sqlStr= getSqlSourceFromAnnotations(method,sqlAnnotationType);
        if (sqlStr != null) {
            final String mappedStatementId = type.getName() + "." + method.getName();
            SqlSource sqlSource=new DynamicSqlSource(configuration,sqlStr);
            StatementType statementType = StatementType.PREPARED;
            SqlCommandType sqlCommandType = SqlCommandType.valueOf(sqlAnnotationType.getSimpleName().toUpperCase(Locale.ENGLISH));
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//            ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
//            String resultMapId;
//            if (resultMapAnnotation == null) {
//                resultMapId = generateResultMapName(method);
//            } else {
//                resultMapId = resultMapAnnotation.value();
//            }
            Type returnType = method.getGenericReturnType();
            Class resultType;
            if(returnType instanceof ParameterizedType){
                Type[] typeArguments = ((ParameterizedType) returnType).getActualTypeArguments();
                resultType= (Class) typeArguments[0];
            }else {
                resultType=returnType.getClass();
            }
            MappedStatement statement=new MappedStatement(mappedStatementId,sqlCommandType,sqlSource,resultType,configuration,resource);
            configuration.addMappedStatement(statement);
        }
    }
    private String getSqlSourceFromAnnotations(Method method,Class<? extends Annotation> sqlAnnotationType) {
        try {
            if (sqlAnnotationType != null) {
                Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
                //这里因为sqlAnnotationType是个超类，故不能直接用sqlAnnotation.value()取值
                String sqlStrings = (String) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
                return sqlStrings;
            }
            return null;
        } catch (Exception e) {
            throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
        }
    }
    private Class<? extends Annotation> chooseAnnotationType(Method method, Set<Class<? extends Annotation>> types) {
        for (Class<? extends Annotation> type : types) {
            Annotation annotation = method.getAnnotation(type);
            if (annotation != null) {
                return type;
            }
        }
        return null;
    }
}
